﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Net.Sockets;
using UnityEngine;

public static class NetManager
{
    //定义套接字
    private static Socket socket;
    //接收缓冲区
    private static ByteArray readBuff;
    //写入队列
    private static Queue<ByteArray> writeQueue;

    private static bool isConnecting = false;

    private static bool isClosing = false;
    
    //消息列表
    private static List<MsgBase> msgList = new List<MsgBase>();
    //消息列表长度
    private static int msgCount = 0;
    //每次Update处理的消息量
    private readonly static int MAX_MESSAGE_FIRE = 10;
    
    //心跳机制
    //是否启用心跳
    public static bool isUsePing = true;
    //心跳间隔事件
    public static int PingInterval = 5;
    //上一次发送Ping的时间
    private static float lastPingTime = 0;
    //上一次收到Pong的时间
    private static float lastPongTime = 0;

    public enum NetEvent
    {
        ConnectSucc=1,
        ConnectFail=2,
        Close=3
    }

    //事件委托类型
    public delegate void EventListener(string err);
    //事件监听列表
    private static Dictionary<NetEvent, EventListener> eventListeners = new Dictionary<NetEvent, EventListener>();
    //添加事件监听
    public static void AddEventListener(NetEvent netEvent, EventListener listener)
    {
        //添加事件
        if (eventListeners.ContainsKey(netEvent)){
            eventListeners[netEvent] += listener;
        }
        //新增事件
        else{
            eventListeners[netEvent] = listener;
        }
    }
    //删除事件监听
    public static void RemoveEventListener(NetEvent netEvent, EventListener listener)
    {
        if (eventListeners.ContainsKey(netEvent)){
            eventListeners[netEvent] -= listener;
        }
    }
    //分发事件
    private static void FireEvent(NetEvent netEvent, string err)
    {
        if(eventListeners.ContainsKey(netEvent)){
            eventListeners[netEvent](err);
        }
    }
    
    //消息委托类型
    public delegate void MsgListener(MsgBase msgBase);
    //消息监听列表
    private static Dictionary<string, MsgListener> msgListeners = new Dictionary<string, MsgListener>();
    //添加消息监听
    public static void AddMsgListener(string msgName, MsgListener listener)
    {
        //添加
        if (msgListeners.ContainsKey(msgName)){
            msgListeners[msgName] += listener;
        }
        //新增
        else{
            msgListeners[msgName] = listener;
        }
    }
    //删除消息监听
    public static void RemoveMsgListener(string msgName, MsgListener listener)
    {
        if (msgListeners.ContainsKey(msgName)){
            msgListeners[msgName] -= listener;
        }
    }
    //分发消息
    private static void FireMsg(string msgName, MsgBase msgBase)
    {
        if(msgListeners.ContainsKey(msgName)){
            msgListeners[msgName](msgBase);
        }
    }

    private static void InitState()
    {
        socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

        readBuff = new ByteArray();

        writeQueue = new Queue<ByteArray>();
        isConnecting = false;
        isClosing = false;
        
        //消息队列
        msgList = new List<MsgBase>();
        msgCount = 0;

        lastPingTime = Time.time;
        lastPongTime = Time.time;

        if (!msgListeners.ContainsKey("MsgPong"))
        {
            AddMsgListener("MsgPong",OnMsgPong);
        }
    }

    #region Connect
    
    public static void Connect(string ip, int port) 
    {
        if (socket!=null&&socket.Connected)
        {
            Debug.Log("Connect fail,already connected");
            return;
        }

        if (isConnecting)
        {
            Debug.Log("connect fail,isConnecting");
            return;
        }

        InitState();

        socket.NoDelay = true;

        isConnecting = true;
        socket.BeginConnect(ip, port, ConnectCallback, socket);
    }

    
    private static void ConnectCallback(IAsyncResult ar)
    {
        try
        {
            Socket socket = (Socket)ar.AsyncState;
            socket.EndConnect(ar);
            Debug.Log("Socket Connect Succ");
            FireEvent(NetEvent.ConnectSucc,"");
            isConnecting = false;
            socket.BeginReceive(readBuff.bytes, readBuff.writeIdx, readBuff.remain, 0, ReceiveCallback, socket);
        }
        catch (SocketException e)
        {
            Debug.Log("Socket Connect fail"+e.ToString());
            FireEvent(NetEvent.ConnectFail,e.ToString());
            isConnecting = false;
        }
    }
    

    #endregion

    #region Close

    public static void Close()
    {
        if (socket==null||!socket.Connected)
        {
            return;
        }

        if (isConnecting)
        {
            return;
        }

        if (writeQueue.Count>0)
        {
            isClosing = true;
        }
        else
        {
            socket.Close();
            FireEvent(NetEvent.Close,"");
        }
    }
    #endregion

    #region Send

    public static void Send(MsgBase msgBase)
    {
        if (socket==null||!socket.Connected)
        {
            return;
        }

        if (isConnecting)
        {
            return;
        }

        if (isClosing)
        {
            return;
        }

        byte[] nameBytes = MsgBase.EncodeName(msgBase);
        byte[] bodyBytes = MsgBase.Encode(msgBase);

        int len = nameBytes.Length + bodyBytes.Length;

        byte[] sendBytes = new byte[2 + len];

        sendBytes[0] = (byte)(len % 256);
        sendBytes[1] = (byte)(len / 256);
        
        Array.Copy(nameBytes,0,sendBytes,2,nameBytes.Length);
        Array.Copy(bodyBytes,0,sendBytes,2+nameBytes.Length,bodyBytes.Length);

        ByteArray ba = new ByteArray(sendBytes);

        int count = 0;
        lock (writeQueue)
        {
            writeQueue.Enqueue(ba);
            count = writeQueue.Count;
        }

        if (count==1)
        {
            socket.BeginSend(sendBytes, 0, sendBytes.Length, 0, SendCallback, socket);
        }
    }

    private static void SendCallback(IAsyncResult ar)
    {
        Socket socket = (Socket)ar.AsyncState;
        if (socket==null||!socket.Connected)
        {
            return;
        }

        int count = socket.EndSend(ar);

        ByteArray ba;
        lock (writeQueue)
        {
            ba = writeQueue.First();
        }

        ba.readIdx += count;
        if (ba.length==0)
        {
            lock (writeQueue)
            {
                writeQueue.Dequeue();
                ba = writeQueue.First();
            }
        }

        if (ba!=null)
        {
            socket.BeginSend(ba.bytes, ba.readIdx, ba.length, 0, SendCallback, socket);
        }
        else if(isClosing)
        {
            socket.Close();
        }
    }

    #endregion

    #region Recieve
    private static void ReceiveCallback(IAsyncResult ar)
    {
        try
        {
            Socket socket = (Socket)ar.AsyncState;
            int count = socket.EndReceive(ar);
            readBuff.writeIdx += count;
            
            OnReceiveData();
            
            if(readBuff.remain < 8){
                readBuff.MoveBytes();
                readBuff.ReSize(readBuff.length*2);
            }
            socket.BeginReceive( readBuff.bytes, readBuff.writeIdx, 
                readBuff.remain, 0, ReceiveCallback, socket);
        }
        catch (SocketException e)
        {
            Debug.Log(e.ToString());
        }
    }

    private static void OnReceiveData()
    {
        if (readBuff.length<=2)
        {
            return;
        }

        int readIdx = readBuff.readIdx;
        byte[] bytes = readBuff.bytes;
        Int16 bodyLength = (Int16)((bytes[readIdx + 1] << 8) | bytes[readIdx]);

        if (readBuff.length<bodyLength)
        {
            return;
        } 

        readBuff.readIdx += 2;

        int nameCount = 0;
        string protoName = MsgBase.DecodeName(readBuff.bytes, readBuff.readIdx, out nameCount);
        
        if (String.IsNullOrEmpty(protoName))
        {
            Debug.Log("OnReceiveData MsgBase.DecodeName fail");
            return;
        }

        readBuff.readIdx += nameCount;
        int bodyCount = bodyLength - nameCount;
        MsgBase msgBase = MsgBase.Decode(protoName, readBuff.bytes, readBuff.readIdx, bodyCount);
        readBuff.readIdx += bodyCount;
        readBuff.CheckAndMoveBytes();

        lock (msgList)
        {
            msgList.Add(msgBase);
        }

        msgCount++;
        if (readBuff.length>2)
        {
            OnReceiveData();
        }
    }

    #endregion

    #region Update

    public static void Update()
    {
        MsgUpdate();
        PingUpdate();
    }
    
    //更新消息
    public static void MsgUpdate(){
        if(msgCount == 0){
            return;
        }
        //重复处理消息
        for(int i = 0; i< MAX_MESSAGE_FIRE; i++){
            //获取第一条消息
            MsgBase msgBase = null;
            lock(msgList){
                if(msgList.Count > 0){
                    msgBase = msgList[0];
                    msgList.RemoveAt(0);
                    msgCount--;
                }
            }
            //分发消息
            if(msgBase != null){
                FireMsg(msgBase.protoName, msgBase);
            }
            //没有消息了
            else{
                break;
            }
        }
    }

    private static void PingUpdate()
    {
        if (!isUsePing)
        {
            return;
        }

        if (Time.time-lastPingTime>PingInterval)
        {
            MsgPing msgPing = new MsgPing();
            Send(msgPing);
            lastPingTime = Time.time;
        }

        if (Time.time-lastPongTime>PingInterval*4)
        {
            Close();
        }
    }

    static void OnMsgPong(MsgBase msgBase)
    {
        lastPongTime = Time.time;
        Debug.Log("MsgPong");
    }

    #endregion
}
